package org.budo.graph.config;

import java.util.List;

import org.budo.graph.druid.spring.aop.interceptor.BudoGraphDruidSpringAopInterceptor;
import org.budo.graph.druid.spring.web.interceptor.BudoGraphDruidSpringWebInterceptor;
import org.budo.graph.service.BudoGraphService;
import org.budo.graph.service.impl.BudoGraphServiceImpl;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.SystemUtil;
import org.budo.support.spring.aop.advisor.BudoPointcutAdvisor;
import org.budo.support.spring.bean.factory.support.BeanBuilder;
import org.budo.support.spring.context.aware.BudoApplicationContextAware;
import org.budo.support.spring.util.SpringUtil;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.ReferenceBean;
import com.alibaba.dubbo.config.spring.ServiceBean;
import com.alibaba.dubbo.registry.RegistryService;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.JedisPool;

/**
 * @author lmw
 * @see org.springframework.web.servlet.handler.MappedInterceptor
 * @see org.budo.spring.cache.interceptor.config.BudoSpringEhCacheInterceptorConfigurer
 * @see org.springframework.web.servlet.config.InterceptorsBeanDefinitionParser
 * @see com.alibaba.druid.spring.boot.autoconfigure.stat.DruidSpringAopConfiguration
 * @see com.alibaba.dubbo.config.spring.schema.DubboNamespaceHandler
 * @see com.alibaba.dubbo.config.spring.schema.DubboBeanDefinitionParser
 */
@Slf4j
@Getter
@Setter
@Accessors(chain = true)
public class BudoGraphConfigurer implements BeanDefinitionRegistryPostProcessor {
    private String aopPatterns;

    private String redisHost;

    private Integer redisPort = 6379;

    private String redisPassword;

    // spring.dubbo.registry.address
    private String dubboRegistryAddress;

    // spring.dubbo.application.name
    private String dubboApplicationName = dubboApplicationName();

    private JedisPool jedisPool;

    public static String dubboApplicationName() {
        String name = SystemUtil.getEnvOrProperty("user.dir", "dubboApplicationName");
        char[] chars = name.toCharArray();
        StringBuffer stringBuffer = new StringBuffer();
        for (char ch : chars) {
            if (Character.isDigit(ch) || Character.isLetter(ch) || ch == '-' || ch == '_' || ch == '.') {
                stringBuffer.append(ch);
            }
        }
        return stringBuffer.toString();
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        log.info("#20 postProcessBeanFactory, beanFactory=" + beanFactory);
    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        log.info("#25 postProcessBeanDefinitionRegistry, registry=" + registry);

        this.registryBudoApplicationContextAware(registry);

        this.registryBudoGraphWebInterceptor(registry);

        this.registryBudoGraphAopInterceptor(registry);
        this.registryBudoGraphAopAdvisor(registry);

        this.registryDubboApplicationConfig(registry);
        this.registryDubboRegistryConfig(registry);
        this.registryDubboRegistryReference(registry);

        this.registerBudoGraphService(registry);
        this.registryBudoGraphDubboServiceConfig(registry); // 在 registryDubboRegistryConfig 后面
    }

    private void registryDubboRegistryConfig(BeanDefinitionRegistry registry) {
        List<BeanDefinition> registryConfigDefinitions = SpringUtil.getBeanDefinitionListByType(registry, RegistryConfig.class.getName());
        if (null != registryConfigDefinitions && !registryConfigDefinitions.isEmpty()) {
            log.info("#80 registryConfigDefinitions=" + registryConfigDefinitions);
            return;
        }

        if (null == this.getDubboRegistryAddress()) {
            log.info("#91 dubboRegistryAddress=" + this.getDubboRegistryAddress());
            return;
        }

        new BeanBuilder().id("registryConfig") //
                .type(RegistryConfig.class) //
                .property("protocol", "zookeeper") //
                .property("address", this.getDubboRegistryAddress()) //
                .registerTo(registry);
    }

    private void registryBudoGraphDubboServiceConfig(BeanDefinitionRegistry registry) {
        List<BeanDefinition> registryConfigDefinitions = SpringUtil.getBeanDefinitionListByType(registry, RegistryConfig.class.getName());
        if (null == registryConfigDefinitions || registryConfigDefinitions.isEmpty()) {
            log.info("#105 registryConfigDefinitions=" + registryConfigDefinitions);
            return;
        }

        new BeanBuilder().id("budoGraphServiceConfig") //
                .type(ServiceBean.class) //
                .property("interface", BudoGraphService.class.getName()) //
                .ref("ref", "budoGraphService") //
                .registerTo(registry);
    }

    private void registryDubboRegistryReference(BeanDefinitionRegistry registry) {
        new BeanBuilder().id("registryServiceReferenceConfig") //
                .type(ReferenceBean.class) //
                .property("interface", RegistryService.class.getName()) //
                .registerTo(registry);
    }

    private void registryDubboApplicationConfig(BeanDefinitionRegistry registry) {
        List<BeanDefinition> applicationConfigDefinitions = SpringUtil.getBeanDefinitionListByType(registry, ApplicationConfig.class.getName());
        if (null != applicationConfigDefinitions && !applicationConfigDefinitions.isEmpty()) {
            log.info("#85 applicationConfigDefinitions=" + applicationConfigDefinitions);
            return;
        }

        new BeanBuilder().id("applicationConfig") //
                .type(ApplicationConfig.class) //
                .property("name", this.getDubboApplicationName()) //
                .registerTo(registry);
    }

    private void registryBudoApplicationContextAware(BeanDefinitionRegistry registry) {
        List<BeanDefinition> applicationContextAwareDefinitions = SpringUtil.getBeanDefinitionListByType(registry, BudoApplicationContextAware.class.getName());
        if (null != applicationContextAwareDefinitions && !applicationContextAwareDefinitions.isEmpty()) {
            log.info("#139 applicationContextAwareDefinitions=" + applicationContextAwareDefinitions);
            return;
        }

        new BeanBuilder().id("budoApplicationContextAware") //
                .type(BudoApplicationContextAware.class) //
                .registerTo(registry);
    }

    private void registryBudoGraphAopInterceptor(BeanDefinitionRegistry registry) {
        List<BeanDefinition> applicationConfigDefinitions = SpringUtil.getBeanDefinitionListByType(registry, BudoGraphDruidSpringAopInterceptor.class.getName());
        if (null != applicationConfigDefinitions && !applicationConfigDefinitions.isEmpty()) {
            log.info("#85 applicationConfigDefinitions=" + applicationConfigDefinitions);
            return;
        }

        new BeanBuilder().id("budoGraphDruidSpringAopInterceptor") //
                .type(BudoGraphDruidSpringAopInterceptor.class) //
                .registerTo(registry);
    }

    private void registryBudoGraphAopAdvisor(BeanDefinitionRegistry registry) {
        BeanDefinition advisorBean = SpringUtil.getAdvisorBeanDefinitionByAdviceType(registry, BudoGraphDruidSpringAopInterceptor.class.getName());
        if (null != advisorBean) {
            log.info("#96 advisorBean=" + advisorBean);
            return;
        }

        // 通过表达式配置Aop
        if (null != this.getAopPatterns() && !this.getAopPatterns().trim().isEmpty()) {
            new BeanBuilder().id("budo-graph-aspectj") //
                    .type(AspectJExpressionPointcutAdvisor.class) //
                    .property("expression", this.getAopPatterns()) //
                    .ref("advice", "budoGraphDruidSpringAopInterceptor")//
                    .registerTo(registry);
            return;
        }

        // 自动Aop
        new BeanBuilder().id("budo-graph-advisor") //
                .type(BudoPointcutAdvisor.class) //
                .ref("advice", "budoGraphDruidSpringAopInterceptor")//
                .registerTo(registry);
    }

    // 判断已存在
    private void registryBudoGraphWebInterceptor(BeanDefinitionRegistry registry) {
        new BeanBuilder().id("budoGraphDruidSpringWebInterceptor") //
                .type(BudoGraphDruidSpringWebInterceptor.class) //
                .registerTo(registry);
    }

    private void registerBudoGraphService(BeanDefinitionRegistry registry) {
        List<BeanDefinition> budoGraphServiceDefinitions = SpringUtil.getBeanDefinitionListByType(registry, BudoGraphServiceImpl.class.getName());
        if (null != budoGraphServiceDefinitions && !budoGraphServiceDefinitions.isEmpty()) {
            log.info("#126 budoGraphServiceDefinitions=" + budoGraphServiceDefinitions);
            return;
        }

        BeanBuilder budoGraphService = new BeanBuilder().id("budoGraphService").type(BudoGraphServiceImpl.class);
        if (null != this.getJedisPool()) {
            budoGraphService.property("jedisPool", this.getJedisPool());
        } else if (null != this.getRedisHost()) {
            new BeanBuilder().id("graphJedisPool") //
                    .type(ReflectUtil.classForName("org.budo.jedis.pool.factory.BudoJedisPoolFactory")) //
                    .property("host", this.getRedisHost()) //
                    .property("port", this.getRedisPort()) //
                    .property("password", this.getRedisPassword()) //
                    .registerTo(registry);
            budoGraphService.ref("jedisPool", "graphJedisPool");
        }

        budoGraphService.registerTo(registry);
    }
}